package io.gitee.twoke.generator.engine.impl.cfg;

import io.gitee.twoke.generator.engine.GenerateEngine;
import io.gitee.twoke.generator.engine.GenerateEngineConfiguration;
import io.gitee.twoke.generator.engine.impl.GenerateEngineImpl;
import io.gitee.twoke.generator.engine.impl.ServiceImpl;
import io.gitee.twoke.generator.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory;
import io.gitee.twoke.generator.engine.impl.db.DbIdGenerator;
import io.gitee.twoke.generator.engine.impl.db.DbSqlSessionFactory;
import io.gitee.twoke.generator.engine.impl.generate.method.GlobalMethodManager;
import io.gitee.twoke.generator.engine.impl.generate.method.GlobalMethodManagerImpl;
import io.gitee.twoke.generator.engine.impl.generate.method.impl.EntityGlobalMethod;
import io.gitee.twoke.generator.engine.impl.generate.output.DefaultOutputInterceptorChain;
import io.gitee.twoke.generator.engine.impl.generate.output.OutputExecutor;
import io.gitee.twoke.generator.engine.impl.generate.output.OutputFileExecutor;
import io.gitee.twoke.generator.engine.impl.generate.output.OutputInterceptorChain;
import io.gitee.twoke.generator.engine.impl.generate.parameter.EntityParameterLoader;
import io.gitee.twoke.generator.engine.impl.generate.parameter.GlobalMethodParameterLoader;
import io.gitee.twoke.generator.engine.impl.generate.parameter.ParameterLoaderManager;
import io.gitee.twoke.generator.engine.impl.generate.parameter.ParameterLoaderManagerImpl;
import io.gitee.twoke.generator.engine.impl.interceptor.*;
import io.gitee.twoke.generator.engine.impl.persistence.*;
import io.gitee.twoke.generator.engine.impl.persistence.cache.EntityCache;
import io.gitee.twoke.generator.engine.impl.persistence.cache.EntityCacheImpl;
import io.gitee.twoke.generator.engine.impl.persistence.entity.*;
import io.gitee.twoke.generator.engine.impl.persistence.entity.data.impl.*;
import io.gitee.twoke.generator.engine.impl.runtime.Clock;
import io.gitee.twoke.generator.engine.impl.util.DefaultClockImpl;
import io.gitee.twoke.generator.engine.impl.util.IoUtil;
import io.gitee.twoke.generator.engine.impl.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.apache.velocity.app.VelocityEngine;

import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.*;


@Slf4j
public abstract class GenerateEngineConfigurationImpl extends GenerateEngineConfiguration {

    public static final String DEFAULT_MYBATIS_MAPPING_FILE = "db/mapping/mappings.xml";
    public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING= 4000;
    public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING= 2000;

    protected SqlSessionFactory sqlSessionFactory;
    protected TransactionFactory transactionFactory;

    protected Set<Class<?>> customMybatisMappers;
    protected Set<String> customMybatisXMLMappers;
    // SESSION FACTORIES /////////////////////////////////////////////////////////

    protected List<SessionFactory> customSessionFactories;
    protected DbSqlSessionFactory dbSqlSessionFactory;
    protected Map<Class<?>, SessionFactory> sessionFactories;
    // ID GENERATOR ///////////////////////////////////////////////////////////////

    protected IdGenerator idGenerator;
    protected DataSource idGeneratorDataSource;
    protected String idGeneratorDataSourceJndiName;

    // COMMAND EXECUTORS ////////////////////////////////////////////////////////

    protected CommandConfig defaultCommandConfig;
    protected CommandConfig schemaCommandConfig;

    protected CommandInterceptor commandInvoker;

    protected List<CommandInterceptor> customPreCommandInterceptors;
    protected List<CommandInterceptor> customPostCommandInterceptors;

    protected List<CommandInterceptor> commandInterceptors;

    protected CommandExecutor commandExecutor;

    protected boolean usingRelationalDatabase = true;

    protected boolean enableVerboseExecutionTreeLogging;

    protected boolean isBulkInsertEnabled = true;
    protected int maxNrOfStatementsInBulkInsert = 100;

    public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 70; // currently Execution has most params (28). 2000 / 28 = 71.

    protected CommandContextFactory commandContextFactory;
    protected TransactionContextFactory transactionContextFactory;

    // DataManager
    private PropertyDataManager propertyDataManager;
    private TemplateDataManager templateDataManager;
    private TemplateGroupDataManager templateGroupDataManager;
    private TemplateFileDataManager templateFileDataManager;
    private DataTableDataManager dataTableDataManager;
    private DataTableColumnDataManager dataTableEntityDataManager;

    // EntityManager ////////////////////////////////////////////////////////
    protected PropertyEntityManager propertyEntityManager;
    protected TemplateEntityManager templateEntityManager;
    protected TemplateGroupEntityManager templateGroupEntityManager;
    protected TemplateFileEntityManager templateFileEntityManager;
    protected DataTableEntityManager dataTableEntityManager;
    protected DataTableColumnEntityManager dataTableColumnEntityManager;

    protected VelocityEngine velocityEngine;
    protected DefaultOutputInterceptorChain outputInterceptorChain;
    protected GlobalMethodManager globalMethodManager;
    protected ParameterLoaderManager parameterLoaderManager;

    protected Map<Object, Object> beans;

    // buildGenerateEngine
    // ///////////////////////////////////////////////////////
    @Override
    public GenerateEngine buildGenerateEngine() {
        init();
        GenerateEngineImpl generateEngine = new GenerateEngineImpl(this);
        return generateEngine;
    }

    // init
    // ///////////////////////////////////////////////////////
    public void init() {
        if (usingRelationalDatabase) {
            initDataSource();
        }

        initBeans();
        initCommandContextFactory();
        initTransactionContextFactory();
        initCommandExecutors();
        initIdGenerator();
        initServices();

        initTransactionFactory();
        if (usingRelationalDatabase) {
            initSqlSessionFactory();
        }

        initSessionFactories();
        initDataManagers();
        initEntityManagers();
        initClock();
        initVelocityEngine();
        initOutputInterceptorChain();
        initGlobalMethodManager();
        initParameterLoaderManager();
    }

    public void initBeans() {
        if (beans == null) {
            beans = new HashMap<Object, Object>();
        }
    }

    private void initParameterLoaderManager() {
        if (parameterLoaderManager == null) {
            parameterLoaderManager = new ParameterLoaderManagerImpl();

            parameterLoaderManager.add(new GlobalMethodParameterLoader(globalMethodManager));
            parameterLoaderManager.add(new EntityParameterLoader());
        }
    }

    private void initGlobalMethodManager() {
        if (globalMethodManager == null) {
            globalMethodManager = new GlobalMethodManagerImpl();
            globalMethodManager.register(EntityGlobalMethod.class, EntityGlobalMethod.instance());
        }
    }

    private void initOutputInterceptorChain() {
        if (outputInterceptorChain == null) {
            this.outputInterceptorChain = new DefaultOutputInterceptorChain(new OutputFileExecutor());
        }
    }

    private void initVelocityEngine() {
        if (velocityEngine == null) {
            this.velocityEngine = new VelocityEngine();
            this.velocityEngine.init();
        }
    }


    public void initTransactionContextFactory() {
        if (transactionContextFactory == null) {
            transactionContextFactory = new StandaloneMybatisTransactionContextFactory();
        }
    }

    // Clock
    // ////////////////////////////////////////////////
    public void initClock() {
        if (clock == null) {
            clock = new DefaultClockImpl();
        }
    }

    // EntityManagers
    // ////////////////////////////////////////////////
    private void initEntityManagers() {
        if (propertyEntityManager == null) {
            propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager);
        }
        if (templateEntityManager == null) {
            templateEntityManager = new TemplateEntityManagerImpl(this, templateDataManager);
        }
        if (templateGroupEntityManager == null) {
            templateGroupEntityManager = new TemplateGroupEntityManagerImpl(this, templateGroupDataManager);
        }
        if (templateFileEntityManager == null) {
            templateFileEntityManager = new TemplateFileEntityManagerImpl(this, templateFileDataManager);
        }
        if (dataTableEntityManager == null) {
            dataTableEntityManager = new DataTableEntityManagerImpl(this, dataTableDataManager);
        }
        if (dataTableColumnEntityManager == null) {
            dataTableColumnEntityManager = new DataTableColumnEntityManagerImpl(this, dataTableEntityDataManager);
        }
    }


    // DataManagers
    // ////////////////////////////////////////////////
    private void initDataManagers() {
        if (propertyDataManager == null) {
            propertyDataManager = new MybatisPropertyDataManager(this);
        }

        if (templateDataManager == null) {
            templateDataManager = new MybatisTemplateDataManager(this);
        }

        if (templateFileDataManager == null) {
            templateFileDataManager = new MybatisTemplateFileDataManager(this);
        }

        if (templateGroupDataManager == null) {
            templateGroupDataManager = new MybatisTemplateGroupDataManager(this);
        }

        if (dataTableDataManager == null) {
            dataTableDataManager = new MybatisDataTableDataManager(this);
        }

        if (dataTableEntityDataManager == null) {
            dataTableEntityDataManager = new MybatisDataTableColumnDataManager(this);
        }
    }

    // SessionFactories
    // ////////////////////////////////////////////////

    public void initSessionFactories() {
        if (sessionFactories == null) {
            sessionFactories = new HashMap<Class<?>, SessionFactory>();

            if (usingRelationalDatabase) {
                initDbSqlSessionFactory();
            }

            addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
        }

        if (customSessionFactories != null) {
            for (SessionFactory sessionFactory : customSessionFactories) {
                addSessionFactory(sessionFactory);
            }
        }
    }

    public void initDbSqlSessionFactory() {
        if (dbSqlSessionFactory == null) {
            dbSqlSessionFactory = createDbSqlSessionFactory();
        }
        dbSqlSessionFactory.setDatabaseType(databaseType);
        dbSqlSessionFactory.setIdGenerator(idGenerator);
        dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory);
        dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix);
        dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema);
        dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog);
        dbSqlSessionFactory.setDatabaseSchema(databaseSchema);
        dbSqlSessionFactory.setBulkInsertEnabled(isBulkInsertEnabled, databaseType);
        dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert);
        addSessionFactory(dbSqlSessionFactory);
    }

    public DbSqlSessionFactory createDbSqlSessionFactory() {
        return new DbSqlSessionFactory();
    }

    public void addSessionFactory(SessionFactory sessionFactory) {
        sessionFactories.put(sessionFactory.getSessionType(), sessionFactory);
    }

    // CommandExecutors
    // ////////////////////////////////////////////////

    public void initCommandExecutors() {
        initDefaultCommandConfig();
        initSchemaCommandConfig();
        initCommandInvoker();
        initCommandInterceptors();
        initCommandExecutor();
    }

    private void initCommandExecutor() {
        if (commandExecutor == null) {
            CommandInterceptor first = initInterceptorChain(commandInterceptors);
            commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
        }
    }

    public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
        if (chain == null || chain.isEmpty()) {
            throw new RuntimeException("invalid command interceptor chain configuration: " + chain);
        }
        for (int i = 0; i < chain.size() - 1; i++) {
            chain.get(i).setNext(chain.get(i + 1));
        }
        return chain.get(0);
    }

    private void initCommandInterceptors() {
        if (commandInterceptors == null) {
            commandInterceptors = new ArrayList<CommandInterceptor>();
            if (customPreCommandInterceptors != null) {
                commandInterceptors.addAll(customPreCommandInterceptors);
            }
            commandInterceptors.addAll(getDefaultCommandInterceptors());
            if (customPostCommandInterceptors != null) {
                commandInterceptors.addAll(customPostCommandInterceptors);
            }
            commandInterceptors.add(commandInvoker);
        }
    }

    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
        List<CommandInterceptor> interceptors = new ArrayList<CommandInterceptor>();
//        interceptors.add(new LogInterceptor());

        CommandInterceptor transactionInterceptor = createTransactionInterceptor();
        if (transactionInterceptor != null) {
            interceptors.add(transactionInterceptor);
        }

        if (commandContextFactory != null) {
            interceptors.add(new CommandContextInterceptor(commandContextFactory, this));
        }

        if (transactionContextFactory != null) {
            interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
        }

        return interceptors;
    }

    public abstract CommandInterceptor createTransactionInterceptor();

    private void initCommandInvoker() {
        if (commandInvoker == null) {
            if (enableVerboseExecutionTreeLogging) {
//                commandInvoker = new DebugCommandInvoker();
            } else {
                commandInvoker = new CommandInvoker();
            }
        }
    }

    private void initSchemaCommandConfig() {
        if (schemaCommandConfig == null) {
            schemaCommandConfig = new CommandConfig().transactionNotSupported();
        }
    }

    private void initDefaultCommandConfig() {
        if (defaultCommandConfig == null) {
            defaultCommandConfig = new CommandConfig();
        }
    }

    // id generator
    // /////////////////////////////////////////////////////////////

    public void initIdGenerator() {
        if (idGenerator == null) {
            CommandExecutor idGeneratorCommandExecutor = null;
            if (idGeneratorDataSource != null) {
                GenerateEngineConfigurationImpl generateEngineConfiguration = new StandaloneGenerateEngineConfiguration();
                generateEngineConfiguration.setDataSource(idGeneratorDataSource);
                generateEngineConfiguration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_FALSE);
                generateEngineConfiguration.init();
                idGeneratorCommandExecutor = generateEngineConfiguration.getCommandExecutor();
            } else if (idGeneratorDataSourceJndiName != null) {
                GenerateEngineConfigurationImpl processEngineConfiguration = new StandaloneGenerateEngineConfiguration();
                processEngineConfiguration.setDataSourceJndiName(idGeneratorDataSourceJndiName);
                processEngineConfiguration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_FALSE);
                processEngineConfiguration.init();
                idGeneratorCommandExecutor = processEngineConfiguration.getCommandExecutor();
            } else {
                idGeneratorCommandExecutor = getCommandExecutor();
            }

            DbIdGenerator dbIdGenerator = new DbIdGenerator();
            dbIdGenerator.setIdBlockSize(idBlockSize);
            dbIdGenerator.setCommandExecutor(idGeneratorCommandExecutor);
            dbIdGenerator.setCommandConfig(getDefaultCommandConfig().transactionRequiresNew());
            idGenerator = dbIdGenerator;
        }
    }

    // Services
    // ////////////////////////////////////////////////

    public void initServices() {
    }

    public void initService(Object service) {
        if (service instanceof ServiceImpl) {
            ((ServiceImpl) service).setCommandExecutor(commandExecutor);
        }
    }

    // DataSource
    // ////////////////////////////////////////////////

    public void initDataSource() {
        if (dataSource == null) {
            if (dataSourceJndiName != null) {
                try {
                    dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
                } catch (Exception e) {
                    throw new RuntimeException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e);
                }

            } else if (jdbcUrl != null) {
                if ((jdbcDriver == null) || (jdbcUsername == null)) {
                    throw new RuntimeException("DataSource or JDBC properties have to be specified in a process engine configuration");
                }

//                log.debug("initializing datasource to db: {}", jdbcUrl);

                PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);

                if (jdbcMaxActiveConnections > 0) {
                    pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
                }
                if (jdbcMaxIdleConnections > 0) {
                    pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
                }
                if (jdbcMaxCheckoutTime > 0) {
                    pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
                }
                if (jdbcMaxWaitTime > 0) {
                    pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
                }
                if (jdbcPingEnabled) {
                    pooledDataSource.setPoolPingEnabled(true);
                    if (jdbcPingQuery != null) {
                        pooledDataSource.setPoolPingQuery(jdbcPingQuery);
                    }
                    pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor);
                }
                if (jdbcDefaultTransactionIsolationLevel > 0) {
                    pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel);
                }
                dataSource = pooledDataSource;
            }

            if (dataSource instanceof PooledDataSource) {
                // ACT-233: connection pool of Ibatis is not properly
                // initialized if this is not called!
                ((PooledDataSource) dataSource).forceCloseAll();
            }
        }

        if (databaseType == null) {
            initDatabaseType();
        }
    }

    protected static Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();

    public static final String DATABASE_TYPE_H2 = "h2";
    public static final String DATABASE_TYPE_HSQL = "hsql";
    public static final String DATABASE_TYPE_MYSQL = "mysql";
    public static final String DATABASE_TYPE_ORACLE = "oracle";
    public static final String DATABASE_TYPE_POSTGRES = "postgres";
    public static final String DATABASE_TYPE_MSSQL = "mssql";
    public static final String DATABASE_TYPE_DB2 = "db2";

    public static Properties getDefaultDatabaseTypeMappings() {
        Properties databaseTypeMappings = new Properties();
        databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2);
        databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL);
        databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL);
        databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE);
        databaseTypeMappings.setProperty("PostgreSQL", DATABASE_TYPE_POSTGRES);
        databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL);
        databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2",DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUXPPC64",DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/LINUXPPC64LE",DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2);
        databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2);
        return databaseTypeMappings;
    }

    public void initDatabaseType() {
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            DatabaseMetaData databaseMetaData = connection.getMetaData();
            String databaseProductName = databaseMetaData.getDatabaseProductName();
            log.debug("database product name: '{}'", databaseProductName);
            databaseType = databaseTypeMappings.getProperty(databaseProductName);
            if (databaseType == null) {
                throw new RuntimeException("couldn't deduct database type from database product name '" + databaseProductName + "'");
            }
            log.debug("using database type: {}", databaseType);

            // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement).
            // Especially with executions, with 100 as default, this limit is passed.
            if (DATABASE_TYPE_MSSQL.equals(databaseType)) {
                maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
            }

        } catch (SQLException e) {
            log.error("Exception while initializing Database connection", e);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                log.error("Exception while closing the Database connection", e);
            }
        }
    }


    // myBatis SqlSessionFactory
    // ////////////////////////////////////////////////
    public void initTransactionFactory() {
        if (transactionFactory == null) {
            if (transactionsExternallyManaged) {
                transactionFactory = new ManagedTransactionFactory();
            } else {
                transactionFactory = new JdbcTransactionFactory();
            }
        }
    }

    public void initSqlSessionFactory() {
        if (sqlSessionFactory == null) {
            InputStream inputStream = null;
            try {
                inputStream = getMyBatisXmlConfigurationStream();

                Environment environment = new Environment("default", transactionFactory, dataSource);
                Reader reader = new InputStreamReader(inputStream);
                Properties properties = new Properties();
                properties.put("prefix", databaseTablePrefix);
                String wildcardEscapeClause = "";
                if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) {
                    wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'";
                }
                properties.put("wildcardEscapeClause", wildcardEscapeClause);
                //set default properties
                properties.put("limitBefore" , "");
                properties.put("limitAfter" , "");
                properties.put("limitBetween" , "");
                properties.put("limitOuterJoinBetween" , "");
                properties.put("limitBeforeNativeQuery" , "");
                properties.put("orderBy" , "order by ${orderByColumns}");
                properties.put("blobType" , "BLOB");
                properties.put("boolValue" , "TRUE");

                if (databaseType != null) {
                    properties.load(getResourceAsStream("db/properties/"+databaseType+".properties"));
                }

                Configuration configuration = initMybatisConfiguration(environment, reader, properties);
                sqlSessionFactory = new DefaultSqlSessionFactory(configuration);

            } catch (Exception e) {
                throw new RuntimeException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
            } finally {
                IoUtil.closeSilently(inputStream);
            }
        }
    }

    public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties);
        Configuration configuration = parser.getConfiguration();

        if(databaseType != null) {
            configuration.setDatabaseId(databaseType);
        }

        configuration.setEnvironment(environment);

        initMybatisTypeHandlers(configuration);
        initCustomMybatisMappers(configuration);

        configuration = parseMybatisConfiguration(configuration, parser);
        return configuration;
    }

    public void initMybatisTypeHandlers(Configuration configuration) {
//        configuration.getTypeHandlerRegistry().register(VariableType.class, JdbcType.VARCHAR, new IbatisVariableTypeHandler());
    }

    public void initCustomMybatisMappers(Configuration configuration) {
        if (getCustomMybatisMappers() != null) {
            for (Class<?> clazz : getCustomMybatisMappers()) {
                configuration.addMapper(clazz);
            }
        }
    }

    public Set<Class<?>> getCustomMybatisMappers() {
        return customMybatisMappers;
    }

    public Configuration parseMybatisConfiguration(Configuration configuration, XMLConfigBuilder parser) {
        return parseCustomMybatisXMLMappers(parser.parse());
    }

    public Configuration parseCustomMybatisXMLMappers(Configuration configuration) {
        if (getCustomMybatisXMLMappers() != null)
            // see XMLConfigBuilder.mapperElement()
            for (String resource : getCustomMybatisXMLMappers()) {
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
            }
        return configuration;
    }

    public Set<String> getCustomMybatisXMLMappers() {
        return customMybatisXMLMappers;
    }

    protected InputStream getResourceAsStream(String resource) {
        return ReflectUtil.getResourceAsStream(resource);
    }

    public InputStream getMyBatisXmlConfigurationStream() {
        return getResourceAsStream(DEFAULT_MYBATIS_MAPPING_FILE);
    }

    // Other
    // ////////////////////////////////////////////////
    public void initCommandContextFactory() {
        if (commandContextFactory == null) {
            commandContextFactory = new CommandContextFactory();
        }
        commandContextFactory.setGenerateEngineConfiguration(this);
    }

    // getters and setters
    // ////////////////////////////////////////////////

    public GenerateEngineConfigurationImpl setClock(Clock clock) {
        if (this.clock == null) {
            this.clock = clock;
        } else {
            this.clock.setCurrentCalendar(clock.getCurrentCalendar());
        }
        return this;
    }

    public void resetClock() {
        if (this.clock != null) {
            clock.reset();
        }
    }

    public int getMaxNrOfStatementsInBulkInsert() {
        return maxNrOfStatementsInBulkInsert;
    }

    public void setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) {
        this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert;
    }

    public boolean isBulkInsertEnabled() {
        return isBulkInsertEnabled;
    }

    public void setBulkInsertEnabled(boolean bulkInsertEnabled) {
        isBulkInsertEnabled = bulkInsertEnabled;
    }

    public boolean isUsingRelationalDatabase() {
        return usingRelationalDatabase;
    }

    public void setUsingRelationalDatabase(boolean usingRelationalDatabase) {
        this.usingRelationalDatabase = usingRelationalDatabase;
    }

    public void setCustomMybatisXMLMappers(Set<String> customMybatisXMLMappers) {
        this.customMybatisXMLMappers = customMybatisXMLMappers;
    }

    public void setCustomMybatisMappers(Set<Class<?>> customMybatisMappers) {
        this.customMybatisMappers = customMybatisMappers;
    }

    public TransactionFactory getTransactionFactory() {
        return transactionFactory;
    }

    public void setTransactionFactory(TransactionFactory transactionFactory) {
        this.transactionFactory = transactionFactory;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public boolean isEnableVerboseExecutionTreeLogging() {
        return enableVerboseExecutionTreeLogging;
    }

    public void setEnableVerboseExecutionTreeLogging(boolean enableVerboseExecutionTreeLogging) {
        this.enableVerboseExecutionTreeLogging = enableVerboseExecutionTreeLogging;
    }

    public CommandExecutor getCommandExecutor() {
        return commandExecutor;
    }

    public void setCommandExecutor(CommandExecutor commandExecutor) {
        this.commandExecutor = commandExecutor;
    }

    public List<CommandInterceptor> getCommandInterceptors() {
        return commandInterceptors;
    }

    public void setCommandInterceptors(List<CommandInterceptor> commandInterceptors) {
        this.commandInterceptors = commandInterceptors;
    }

    public List<CommandInterceptor> getCustomPostCommandInterceptors() {
        return customPostCommandInterceptors;
    }

    public void setCustomPostCommandInterceptors(List<CommandInterceptor> customPostCommandInterceptors) {
        this.customPostCommandInterceptors = customPostCommandInterceptors;
    }

    public List<CommandInterceptor> getCustomPreCommandInterceptors() {
        return customPreCommandInterceptors;
    }

    public void setCustomPreCommandInterceptors(List<CommandInterceptor> customPreCommandInterceptors) {
        this.customPreCommandInterceptors = customPreCommandInterceptors;
    }

    public CommandInterceptor getCommandInvoker() {
        return commandInvoker;
    }

    public void setCommandInvoker(CommandInterceptor commandInvoker) {
        this.commandInvoker = commandInvoker;
    }

    public CommandConfig getSchemaCommandConfig() {
        return schemaCommandConfig;
    }

    public void setSchemaCommandConfig(CommandConfig schemaCommandConfig) {
        this.schemaCommandConfig = schemaCommandConfig;
    }

    public CommandConfig getDefaultCommandConfig() {
        return defaultCommandConfig;
    }

    public void setDefaultCommandConfig(CommandConfig defaultCommandConfig) {
        this.defaultCommandConfig = defaultCommandConfig;
    }

    public PropertyEntityManager getPropertyEntityManager() {
        return propertyEntityManager;
    }

    public void setPropertyEntityManager(PropertyEntityManager propertyEntityManager) {
        this.propertyEntityManager = propertyEntityManager;
    }

    public List<SessionFactory> getCustomSessionFactories() {
        return customSessionFactories;
    }

    public void setCustomSessionFactories(List<SessionFactory> customSessionFactories) {
        this.customSessionFactories = customSessionFactories;
    }

    public DbSqlSessionFactory getDbSqlSessionFactory() {
        return dbSqlSessionFactory;
    }

    public void setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) {
        this.dbSqlSessionFactory = dbSqlSessionFactory;
    }

    public Map<Class<?>, SessionFactory> getSessionFactories() {
        return sessionFactories;
    }

    public void setSessionFactories(Map<Class<?>, SessionFactory> sessionFactories) {
        this.sessionFactories = sessionFactories;
    }

    public IdGenerator getIdGenerator() {
        return idGenerator;
    }

    public void setIdGenerator(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    public DataSource getIdGeneratorDataSource() {
        return idGeneratorDataSource;
    }

    public void setIdGeneratorDataSource(DataSource idGeneratorDataSource) {
        this.idGeneratorDataSource = idGeneratorDataSource;
    }

    public String getIdGeneratorDataSourceJndiName() {
        return idGeneratorDataSourceJndiName;
    }

    public void setIdGeneratorDataSourceJndiName(String idGeneratorDataSourceJndiName) {
        this.idGeneratorDataSourceJndiName = idGeneratorDataSourceJndiName;
    }

    public CommandContextFactory getCommandContextFactory() {
        return commandContextFactory;
    }

    public void setCommandContextFactory(CommandContextFactory commandContextFactory) {
        this.commandContextFactory = commandContextFactory;
    }

    public TemplateEntityManager getTemplateEntityManager() {
        return templateEntityManager;
    }

    public TemplateDataManager getTemplateDataManager() {
        return templateDataManager;
    }

    public PropertyDataManager getPropertyDataManager() {
        return propertyDataManager;
    }

    public TransactionContextFactory getTransactionContextFactory() {
        return transactionContextFactory;
    }

    public TemplateFileDataManager getTemplateFileDataManager() {
        return templateFileDataManager;
    }

    public TemplateFileEntityManager getTemplateFileEntityManager() {
        return templateFileEntityManager;
    }

    public DataTableEntityManager getDataTableEntityManager() {
        return this.dataTableEntityManager;
    }

    public DataTableColumnEntityManager getDataTableColumnEntityManager() {
        return dataTableColumnEntityManager;
    }

    public TemplateGroupDataManager getTemplateGroupDataManager() {
        return templateGroupDataManager;
    }

    public DataTableDataManager getDataTableDataManager() {
        return dataTableDataManager;
    }

    public DataTableColumnDataManager getDataTableEntityDataManager() {
        return dataTableEntityDataManager;
    }

    public TemplateGroupEntityManager getTemplateGroupEntityManager() {
        return templateGroupEntityManager;
    }

    public VelocityEngine getVelocityEngine() {
        return velocityEngine;
    }

    public OutputInterceptorChain getOutputInterceptorChain() {
        return outputInterceptorChain;
    }

    public OutputExecutor getOutputExecutor() {
        return outputInterceptorChain;
    }

    public GlobalMethodManager getGlobalMethodManager() {
        return this.globalMethodManager;
    }

    public ParameterLoaderManager getParameterLoaderManager() {
        return this.parameterLoaderManager;
    }


    public Map<Object, Object> getBeans() {
        return beans;
    }

    public GenerateEngineConfigurationImpl setBeans(Map<Object, Object> beans) {
        this.beans = beans;
        return this;
    }
}
